/***
    Amrita ITEWS - Copyright (C) 2009 Amrita Vishwa Vidyapeetham, Amritapuri.
                                       (http://www.amrita.edu)
    ***************************************************************************
    This file is part of the Amrita ITEWS distribution.
    Amrita ITEWS is free software; you can redistribute it and/or modify it 
    under the terms of the GNU General Public License (version 2) as published
    by the Free Software Foundation AND MODIFIED BY the Amrita ITEWS exception.
    ***NOTE*** The exception to the GPL is included to allow you to distribute
    a combined work that includes Amrita ITEWS without being obliged to provide
    the source code for proprietary components outside of the Amrita ITEWS
    software. Amrita ITEWS is distributed in the hope that it will be useful, 
    but WITHOUT ANY WARRANTY; without even the implied warranty of 
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
    Public License for more details. You should have received a copy of the GNU
    General Public License and the Amrita ITEWS license exception along with 
    Amrita ITEWS if not then it can be viewed here: 
    http://itews.amrita.ac.in/license.html.

    Documentation, latest information, license and contact details are at:
    http://itews.amrita.ac.in/

    Amrita ITEWS source code can be found at:
    http://code.google.com/p/itews

    The initial code-base of Amrita ITEWS was developed by Amrita Vishwa 
    Vidyapeetham as part of the project titled,"Intelligent & Interactive 
    Telematics using Emerging Wireless Technologies for Transport Systems 
    (ITEWS)" sponsored by Technology Information Forecasting and Assessment 
    Council (TIFAC), India.
***/
package mediatriggerservice;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Hashtable;
import org.knopflerfish.service.log.LogRef;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;

public class Activator implements BundleActivator, EventHandler  
{
	private static final String CURRENT_ROUTE = "currentRoute";
	private static final String CURRENT_NODE ="currentNode";
	private static final String DETERMINED_NODE_SEQNO_FROM_AUTOPILOT = "determinednodeseqno";
	private static final String CURRENT_NODE_SEQNO ="currentNodeSeqNo";
	private static final String LAST_NODE ="lastNode";
	private static final String NO_NODE ="nonode";
	private static final String NO_ROUTE ="noroute";
	private static final String DEPARTURE_FROM_NODE= "D";
	private static final String ARRIVAL_AT_NODE = "A";
	private static final String NEW_MEDIA_SET_ARRIVED = "newMediaSet";
	public static BundleContext bc = null;	
	private  LogRef log;
	private MediaTriggerServiceConfig config;
	private ServiceRegistration serviceRegistration; 
	private MediaPlayTrigger mediaPlayTrigger;
	private DBConnectionPool dbConnectionPool ;
	private EventAdmin eventAdmin;
	private String currentNodeCode = "";
	private String currentRouteCode = "";
	private String mediaPlayerQueueDelete = "DELETE FROM media_player_queue";
	private String lastMediaPlayedDelete = "DELETE FROM last_media_played";
	private String persistentDataQuery = "SELECT current_node_code, current_route_code, current_node_seq_no FROM persistent_data LIMIT 1";
	private String persistentDataUpdateNode = "UPDATE persistent_data SET current_node_code=?, current_node_seq_no=? WHERE id=1";
	private String triggerDescriptorsQuery = "SELECT id FROM trigger_descriptors WHERE node_code=? AND route_schedule_code=? AND trigger_condition=?";
	private String routeScheduleNodeassocQueryForNodeCode = "SELECT node_code FROM route_schedule_node_assoc WHERE route_schedule_code=? AND node_seq_no=? LIMIT 1";
	
	public void start(BundleContext bc) throws Exception 
	{
		Activator.bc = bc;
		log = new LogRef(bc);
		ServiceReference reference = bc.getServiceReference (EventAdmin.class.getName());
		eventAdmin = (EventAdmin)bc.getService(reference);
		try
		{
			config = MediaTriggerServiceConfig.getConfig();
		}
		catch(Exception e)
		{
			log.error(Activator.bc.getBundle().getHeaders().get(Constants.BUNDLE_NAME)+ " failed to start due to configuration file setup error.");
			System.err.println(Activator.bc.getBundle().getHeaders().get(Constants.BUNDLE_NAME)+ " failed to start due to configuration file setup error.");
			Activator.bc = null;
			throw e;
		}
		Connection conn = null;
		PreparedStatement preparedStmt1 = null;
		ResultSet rs1 = null;

		try
		{
			dbConnectionPool = DBConnectionPool.getInstance(config.getJdbcConnectionURI());
			conn = dbConnectionPool.getConnection();
			preparedStmt1 = dbConnectionPool.getPreparedStmt(conn, persistentDataQuery);
			rs1 = preparedStmt1.executeQuery();
			if (rs1.next() == true)
			{
				currentNodeCode =  rs1.getString(1).trim();
				currentRouteCode =  rs1.getString(2).trim();
				int currentNodeSqeNo =  rs1.getInt(3);
				if (currentNodeSqeNo == 0)
				{
					currentNodeCode = "";
				}
					
			}
			rs1.close();
			rs1 = null;
			preparedStmt1.close();
			preparedStmt1 = null;
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		finally
		{
    		if (rs1 != null)
    		{
    			try {rs1.close();} catch (SQLException e) {} 
    			rs1 = null;
    		}
    		if (preparedStmt1 != null)
    		{
    			try {preparedStmt1.close();} catch (SQLException e) {} 
    			preparedStmt1 = null;
    		}
			if (conn != null)
			{
				try {conn.close();} catch (SQLException e) {}
				conn = null;
			}
		}
		Hashtable ht = new Hashtable();
		ht.put(EventConstants.EVENT_TOPIC, config.getIncomingEvents());
		serviceRegistration = bc.registerService(EventHandler.class.getName(), this, ht);
		mediaPlayTrigger = new MediaPlayTrigger(eventAdmin);
		System.out.println(bc.getBundle().getHeaders().get(Constants.BUNDLE_NAME)+ " started...");
	}
	
	public synchronized void handleEvent(Event event) 
	{
		String eventTopic = event.getTopic();
		if (eventTopic.equals(CURRENT_NODE))
		{
			String newNodeCode = ((String) event.getProperty(CURRENT_NODE)).trim();
			int newNodeSeqNo = (Integer) event.getProperty(CURRENT_NODE_SEQNO);
			System.out.println("mediatriggerservice: Received new Node "+ newNodeCode + " new nodeseqno "+newNodeSeqNo);
			handleCurrentNodeEvent(newNodeCode, newNodeSeqNo);
		}
		else if (eventTopic.equals(DETERMINED_NODE_SEQNO_FROM_AUTOPILOT))
		{
			String nodeCode = ((String) event.getProperty(CURRENT_NODE)).trim();
			int newNodeSeqNo = (Integer) event.getProperty(CURRENT_NODE_SEQNO);
			System.out.println("mediatriggerservice: Received from Autopilot new Node "+ nodeCode + " new nodeseqno "+newNodeSeqNo);
			handleCurrentNodeEventFromAutoPilot(nodeCode, newNodeSeqNo);
		}
		else if (eventTopic.equals(CURRENT_ROUTE))
		{
			currentRouteCode = ((String) event.getProperty(CURRENT_ROUTE)).trim();
			if (currentRouteCode.equals(NO_ROUTE))
			{
				currentNodeCode = "";
			}
			System.out.println("Received CURRENT route at mediatriggerservice "+ currentRouteCode);
		}
	}
	
	private synchronized void handleCurrentNodeEvent(String newNodeCode, int newNodeSeqNo)
	{
		Connection conn = null;
		PreparedStatement preparedStmt = null;
		PreparedStatement preparedStmt1 = null;
		PreparedStatement preparedStmt2 = null;
		PreparedStatement preparedStmt3 = null;
		ResultSet rs1 = null;
		try
		{
			log.debug("mediatriggerservice: New Node Code:"+newNodeCode + "Previous Node Code :"+currentNodeCode);
			if (!currentRouteCode.equals("") && !currentRouteCode.equals(NO_ROUTE) && !currentNodeCode.equals(newNodeCode))
			{
				String triggerCondition = null;
				String playNodeCode = null;
				if ((currentNodeCode.equals("") || currentNodeCode.equals(NO_NODE)) && !newNodeCode.equals(NO_NODE))
				{
					triggerCondition = ARRIVAL_AT_NODE;
					playNodeCode = newNodeCode;
				}
				else if (!currentNodeCode.equals(NO_NODE) && newNodeCode.equals(NO_NODE))
				{
					triggerCondition = DEPARTURE_FROM_NODE;
					playNodeCode = currentNodeCode;
				}
				else if (!currentNodeCode.equals(NO_NODE) && !newNodeCode.equals(NO_NODE))
				{
					triggerCondition = ARRIVAL_AT_NODE;
					playNodeCode = newNodeCode;
				}
				System.out.println("Trigger Condition " + triggerCondition);
				conn = dbConnectionPool.getConnection();
				preparedStmt = dbConnectionPool.getPreparedStmt(conn, triggerDescriptorsQuery);
				preparedStmt.setString(1, playNodeCode);
				preparedStmt.setString(2, currentRouteCode);
				preparedStmt.setString(3, triggerCondition);
				rs1 = preparedStmt.executeQuery();
				if (rs1.next() == true)
				{
					preparedStmt1 = dbConnectionPool.getPreparedStmt(conn, mediaPlayerQueueDelete);
					preparedStmt1.executeUpdate();
					preparedStmt1.close();
					preparedStmt1 = null;
					
					preparedStmt2 = dbConnectionPool.getPreparedStmt(conn, lastMediaPlayedDelete);
					preparedStmt2.executeUpdate();
					preparedStmt2.close();
					preparedStmt2 = null;
					
					// Notify Media Player Manager regarding the node change, new media set update and to abort current playing
					Hashtable newMediaSetArrivalInfo = new Hashtable();
					Event event = new Event(NEW_MEDIA_SET_ARRIVED, newMediaSetArrivalInfo);
					this.eventAdmin.postEvent(event);
					
					if (triggerCondition != null)
					{
						mediaPlayTrigger.executeMediaTrigger(currentRouteCode, playNodeCode, currentNodeCode, triggerCondition);
					}
				}
				else
				{
					System.out.println("No media set available for scheduling: " +currentRouteCode + " "+newNodeCode);
				}
				preparedStmt.close();
				preparedStmt = null;
				
				log.debug("mediatriggerservice: updating persistent data with node seq no:" +newNodeSeqNo);
				preparedStmt3 = dbConnectionPool.getPreparedStmt(conn, persistentDataUpdateNode);
				preparedStmt3.setString(1, newNodeCode.trim());
				preparedStmt3.setInt(2, newNodeSeqNo);
				preparedStmt3.executeUpdate();
				preparedStmt3.close();
				preparedStmt3 = null;
				
				//Send last node to both gps and wifi nodelocator bundles
				Hashtable lastNodeInfo = new Hashtable();
				lastNodeInfo.put(LAST_NODE, newNodeCode);
				Event event = new Event(LAST_NODE, lastNodeInfo);
				this.eventAdmin.postEvent(event);

				currentNodeCode = newNodeCode;
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		finally
		{
    		if (rs1 != null)
    		{
    			try {rs1.close();} catch (SQLException e) {} 
    			rs1 = null;
    		}
    		if (preparedStmt != null)
    		{
    			try {preparedStmt.close();} catch (SQLException e) {} 
    			preparedStmt = null;
    		}
			if (preparedStmt1 != null)
    		{
    			try {preparedStmt1.close();} catch (SQLException e) {} 
    			preparedStmt1 = null;
    		}
			if (preparedStmt2 != null)
    		{
    			try {preparedStmt2.close();} catch (SQLException e) {} 
    			preparedStmt2 = null;
    		}
			if (preparedStmt3 != null)
    		{
    			try {preparedStmt3.close();} catch (SQLException e) {} 
    			preparedStmt3 = null;
    		}
			if (conn != null)
			{
				try {conn.close();} catch (SQLException e) {}
				conn = null;
			}
		}
	}

	private synchronized void handleCurrentNodeEventFromAutoPilot(String newNodeCode, int newNodeSeqNo)
	{
		Connection conn = null;
		PreparedStatement preparedStmt = null;
		PreparedStatement preparedStmt1 = null;
		PreparedStatement preparedStmt2 = null;
		ResultSet rs1 = null;
		try
		{
			conn = dbConnectionPool.getConnection();
			
			String triggerCondition = null;
			String playNodeCode = null;

			if (newNodeCode.equals(NO_NODE))
			{
				triggerCondition = DEPARTURE_FROM_NODE;
				playNodeCode = getNodeCodeForSwitchedRoute(newNodeSeqNo);;
			}
			else
			{
				triggerCondition = ARRIVAL_AT_NODE;
				playNodeCode = newNodeCode;
			}
			System.out.println("Trigger Condition " + triggerCondition);
			System.out.println("playNodeCode " + playNodeCode);
			preparedStmt = dbConnectionPool.getPreparedStmt(conn, triggerDescriptorsQuery);
			preparedStmt.setString(1, playNodeCode);
			preparedStmt.setString(2, currentRouteCode);
			preparedStmt.setString(3, triggerCondition);
			rs1 = preparedStmt.executeQuery();
			if (rs1.next() == true)
			{
				preparedStmt1 = dbConnectionPool.getPreparedStmt(conn, mediaPlayerQueueDelete);
				preparedStmt1.executeUpdate();
				preparedStmt1.close();
				preparedStmt1 = null;
				
				preparedStmt2 = dbConnectionPool.getPreparedStmt(conn, lastMediaPlayedDelete);
				preparedStmt2.executeUpdate();
				preparedStmt2.close();
				preparedStmt2 = null;
				
				// Notify Media Player Manager regarding the node change, new media set update and to abort current playing
				Hashtable newMediaSetArrivalInfo = new Hashtable();
				Event event = new Event(NEW_MEDIA_SET_ARRIVED, newMediaSetArrivalInfo);
				this.eventAdmin.postEvent(event);
				
				if (triggerCondition != null)
				{
					mediaPlayTrigger.executeMediaTrigger(currentRouteCode, playNodeCode, currentNodeCode, triggerCondition);
				}
			}
			else
			{
				System.out.println("No media set available for scheduling: " +currentRouteCode + " "+newNodeCode);
			}
			preparedStmt.close();
			preparedStmt = null;
			
			preparedStmt = dbConnectionPool.getPreparedStmt(conn, persistentDataUpdateNode);
			preparedStmt.setString(1, newNodeCode.trim());
			preparedStmt.setInt(2, newNodeSeqNo);
			preparedStmt.executeUpdate();
			preparedStmt.close();
			preparedStmt = null;
			log.debug("mediatriggerservice: persistent data updated");
			
			Hashtable lastNodeInfo = new Hashtable();
			lastNodeInfo.put(LAST_NODE, newNodeCode);
			Event event = new Event(LAST_NODE, lastNodeInfo);
			this.eventAdmin.postEvent(event);

			currentNodeCode = newNodeCode;
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		finally
		{
    		if (rs1 != null)
    		{
    			try {rs1.close();} catch (SQLException e) {} 
    			rs1 = null;
    		}
			if (preparedStmt != null)
			{
				try {preparedStmt.close();} catch (SQLException e) {} 
				preparedStmt = null;
			}
			if (preparedStmt1 != null)
    		{
    			try {preparedStmt1.close();} catch (SQLException e) {} 
    			preparedStmt1 = null;
    		}
			if (preparedStmt2 != null)
    		{
    			try {preparedStmt2.close();} catch (SQLException e) {} 
    			preparedStmt2 = null;
    		}
			if (conn != null)
			{
				try {conn.close();} catch (SQLException e) {}
				conn = null;
			}
		}
	}
	
	private String getNodeCodeForSwitchedRoute(int nodeSeqNo)
	{
		Connection conn = null;
		PreparedStatement preparedStmt = null;
		ResultSet rs = null;
		String nodeCode = "";
		try
		{
			conn = dbConnectionPool.getConnection();
			preparedStmt = dbConnectionPool.getPreparedStmt(conn, routeScheduleNodeassocQueryForNodeCode);
			preparedStmt.setString(1, currentRouteCode);
			preparedStmt.setInt(2, nodeSeqNo);
			rs = preparedStmt.executeQuery();
			if (rs.next() == true)
			{
				nodeCode = rs.getString(1);
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		finally
		{
			if (rs != null)
			{
				try {rs.close();} catch (SQLException e) {} 
				rs = null;
			}
			if (preparedStmt != null)
			{
				try {preparedStmt.close();} catch (SQLException e) {} 
				preparedStmt = null;
			}
			if (conn != null)
			{
				try {conn.close();} catch (SQLException e) {}
				conn = null;
			}
		}
		return nodeCode;
	}
	
	public void stop(BundleContext context) throws Exception 
	{
		if (serviceRegistration!=null)
		{
			serviceRegistration.unregister();
			serviceRegistration = null;
		}
		System.out.println(bc.getBundle().getHeaders().get(Constants.BUNDLE_NAME)+ " stopped!");
		Activator.bc = null;
	}
}
